package com.palm.dev.service;


import com.alibaba.fastjson.JSON;
import com.jfinal.template.Engine;
import com.palm.core.data.Item;
import com.palm.dev.mode.Column;
import com.palm.dev.mode.GenOption;
import com.palm.dev.mode.Table;

import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Init;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.handle.Result;
import org.noear.solon.core.util.PrintUtil;
import org.sagacity.sqltoy.utils.FileUtil;
import org.sagacity.sqltoy.utils.StringUtil;

import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.sql.*;
import java.util.*;

import static com.palm.dev.DevConfig.basePath;

@Component
public class ModelServices {
    @Inject
    DataSource dataSource;
    private Engine engine;
    private static final int OUTPUT_PAGE=2;//页面输出
    @Init
    public void init(){
        engine=Engine.use();
        engine.setToClassPathSourceFactory();
        engine.setDevMode(true);
        engine.setBaseTemplatePath("template");
        engine.addSharedStaticMethod(StringUtil.class);
    }
    /**
     * 模型所在文件夹
     * @return
     */
    public String getModelDir(){
        return basePath+"/src/test/resources/models/";
    }

    /**
     * 获取项目下的模型
     * @return
     */
    public Result loadModels() {
        File dir=new File(getModelDir());
        if(!dir.exists()){
            dir.mkdirs();
        }
        List<Item> modules=new ArrayList<>();
        loadModels(modules,dir,"");
        return Result.succeed(modules);
    }
    private void loadModels(List<Item> modules,File dir,String base){
        for(File f:dir.listFiles()){
            if(f.isDirectory()){
                Item item=new Item(base+"/"+f.getName(),f.getName());
                item.setChildren(new ArrayList<>());
                modules.add(item);
                loadModels(item.getChildren(),f,base+"/"+f.getName());
            }else if(f.getName().endsWith(".json")){
                Item item=new Item(base+"/"+f.getName(),f.getName().replaceFirst("\\.json",""));
                modules.add(item);
            }
        }
    }

    /**
     * 获取默认数据中的表和View
     * @return
     */
    public Result loadTables(){
        try(Connection conn= dataSource.getConnection()) {
            DatabaseMetaData meta = conn.getMetaData();
            String catalog=conn.getCatalog();
            String schema=conn.getSchema();
            ResultSet tables=  meta.getTables(catalog,schema,"%%",new String[]{"TABLE","VIEW"});
            List<Map<String,String>> tableList=new ArrayList<>();
            while (tables.next()) {
                Map<String,String> table=new HashMap<>();
                table.put("name",tables.getString("TABLE_NAME"));
                table.put("type",tables.getString("TABLE_TYPE"));
                tableList.add(table);
            }
            return Result.succeed(tableList);
        } catch (SQLException e) {
            e.printStackTrace();
            return Result.failure(e.getMessage());
        }
    }
    public Result generate(String id, GenOption option){
        File file=new File(getModelDir(),id);
        if(!file.exists()){
            return Result.failure("模型不存在");
        }
        String packagePath=option.getBasePackage().replaceAll("\\.","/");
        Map<String,String> resultSource=new HashMap<>();
        Table table= JSON.parseObject(read(file),Table.class);
        table.setPackageName(option.getBasePackage()+"."+table.getModule());
        //table.setModule(id.substring());
        //生存表单VO
        if(option.isGenFormVo()){
            String path=basePath+"src/main/java/"+packagePath+"/"+table.getModule()+"/domain/"+table.getName()+"Vo.java";
            String out=generate("formvo.tpl",table,option,path);
            if(out!=null){
                resultSource.put("FromVo.java",out);
            }
        }
        //生成实体
        if(option.isGenEntity()){
            String path=basePath+"src/main/java/"+packagePath+"/"+table.getModule()+"/domain/"+table.getName()+".java";
            String out=generate("entity.tpl",table,option,path);
            if(out!=null){
                resultSource.put("Entity.java",out);
            }
        }
        //生成xml
       if(option.isGenMapper()){
           String path=basePath+"src/main/resources/mapper/"+StringUtil.firstToLowerCase(table.getName())+".sql.xml";
           String out=generate("mapper.sql.xml.tpl",table,option,path);
           if(out!=null){
               resultSource.put(StringUtil.firstToLowerCase(table.getName())+".sql.xml",out);
           }
       }
        if(option.isGenMenu()){
            String name=StringUtil.firstToUpperCase(table.getModule())+"Menu.java";
            String path=basePath+"src/main/java/"+packagePath+"/"+table.getModule()+"/"+name;
            String out=genMenu(table,option,path);
            if(out!=null){
                resultSource.put(name,out);
            }
        }
        //生成控制器
        if(option.isGenController()){
            String path=basePath+"src/main/java/"+packagePath+"/"+table.getModule()+"/controller/"+table.getName()+"Controller.java";
            String out=generate("controller.tpl",table,option,path);
            if(out!=null){
                resultSource.put("Controller.java",out);
            }
        }
        if(option.isGenView()){
            String path=basePath+"src/main/resources/static/"+table.getModule()+"/"+StringUtil.firstToLowerCase(table.getName())+".vue";
            String out=generate("list.vue.tpl",table,option,path);
            if(out!=null){
                resultSource.put("ListView.vue",out);
            }
            path=basePath+"src/main/resources/static/"+table.getModule()+"/"+StringUtil.firstToLowerCase(table.getName())+"Dialog.vue";
            out=generate("dialog.vue.tpl",table,option,path);
            if(out!=null){
                resultSource.put("DialogView.vue",out);
            }
        }
        return Result.succeed(resultSource);
    }

    /**
     * 生成菜单配置
     * @param table
     * @param option
     * @param path
     * @return
     */
    private String genMenu(Table table,GenOption option,String path){
        File file=new File(path);
        String source="";
        String name=StringUtil.firstToUpperCase(table.getModule());
        String tbName=table.getName().toUpperCase();
        String sub="    @Menu(value = \""+table.getTitle()+"\", path = \""+table.getModule()+"/"+StringUtil.firstToLowerCase(table.getName())+".vue\", icon = \"icon-file\")\n";
        String menuItem="    String "+tbName+" = ID + \"."+StringUtil.firstToLowerCase(table.getName())+"\";\n";
        sub+=menuItem+"\n";
        boolean modify=true;
        if(option.isGenPriv()){
            sub+="    // "+table.getTitle()+"增删改权限\n";
            sub+="    @Permission(value = \"新增\", parent = "+tbName+")\n";
            sub+="    String "+tbName+"_ADD = "+tbName+" + \".add\";\n";
            sub+="    @Permission(value = \"修改\", parent = "+tbName+")\n";
            sub+="    String "+tbName+"_EDIT = "+tbName+" + \".edit\";\n";
            sub+="    @Permission(value = \"删除\", parent = "+tbName+")\n";
            sub+="    String "+tbName+"_DELETE = "+tbName+" + \".delete\";\n";
        }
        if(!file.exists()){
           source="package "+option.getBasePackage()+"."+table.getModule()+";\n\n"
                    +"import com.palm.core.anno.Menu;\n"
                    +"import com.palm.core.anno.Permission;\n\n";
           source+="@Menu(value = \""+table.getModule()+"\", icon = \"icon-file\", id = "+name+"Menu.ID)\n";
           source+="public interface "+name+"Menu {\n\n";
           source+="    String ID = \""+table.getName()+"\";\n\n";
           source+=sub;
           source+="}";
        }else{
            source = read(file);
            if(source.indexOf("String "+tbName+" =")==-1){//没有菜单
               int idx= source.lastIndexOf("}");
               source=source.substring(0,idx)+"\n"+sub+"}";
            }else{
                modify=false;
            }
        }
        if(option.getOutput()==OUTPUT_PAGE) {
            return source;
        }else{
            if(modify) {
                write(file, source);
                PrintUtil.blueln("生成："+path);
            }
            return null;
        }
    }
    private String generate(String template,Table table,GenOption option,String path){
        if(option.getOutput()==OUTPUT_PAGE){
            return generate(template,table,option);
        }else{
            //String path=basePath+"src/main/"+packagePath+"/"+table.getModelName()+"/entity/"+table.getCRUDPrefix()+".java";
            File outFile=new File(path);
            if(!option.isOverwrite()&&outFile.exists()){
                PrintUtil.greenln("跳过文件："+path);
                return null;
            }else{
                String out= generate(template,table,option);
                write(outFile,out);
                PrintUtil.blueln("生成："+path);
            }
            return null;
        }
    }
    private String generate(String template,Table table,GenOption option){
        Map<String,Object> params=new HashMap<>();
        params.put("table",table);
        params.put("nl","\n");//输出换行
        params.put("nt","\t");//输出table
        params.put("option",option);
        return engine.getTemplate(template).renderToString(params);
    }
    public Table genTable(String tableName,String prefix){
        try(Connection conn= dataSource.getConnection()) {
            return generateModelFrom(conn,tableName,prefix);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 通过数据库表生成表模型
     *
     * @param conn
     * @param table
     * @return
     */
    private Table generateModelFrom(Connection conn, String table,String prefix) {
        try {
            DatabaseMetaData meta = conn.getMetaData();
            Table dm = new Table();
            String name=table;
            if(StringUtil.isNotBlank(prefix)){
                name=name.replaceFirst("^("+prefix.replaceAll(",",")|(")+")|(\\w{1,2}_)","");
            }else{
                name=name.replaceAll("^(\\w{1,2}_)","");
            }
            name=StringUtil.firstToUpperCase(name);
            dm.setName(name);
            dm.setCode(table);
            dm.setTitle(table);
            String catalog=conn.getCatalog();
            String schema=conn.getSchema();

            ResultSet tbRs=  meta.getTables(catalog,schema,table,new String[]{"TABLE","VIEW"});
            if (tbRs.next()){
                dm.setType(tbRs.getString("TABLE_TYPE"));
            }else{
                PrintUtil.error("数据库中没有表 '"+table+"'");
            }
            tbRs.close();

            if("VIEW".equals(dm.getType())){
                Statement stm= conn.createStatement();
                ResultSet rs= stm.executeQuery("select * from "+table+" where 0");
                ResultSetMetaData rm = rs.getMetaData();
                int c=rm.getColumnCount();
                for(int i=1;i<=c;i++){
                    Column dc = new Column();
                    dc.setCode(rm.getColumnLabel(i));
                    dc.setPrimaryKey(false);
                    dc.setDataType(rm.getColumnTypeName(i));

                    dc.setLength(rm.getColumnDisplaySize(i));
                    dc.setVisibleInForm(true);
                    dc.setVisibleInList(true);
                    int dataType = rm.getColumnType(i);
                    setType(dataType,dc);
                    dc.setType(getSqlDataType(dataType));
                    dc.setName(rm.getColumnLabel(i));
                    dc.setMandatory(false);
                    dc.setSearchCondition(false);
                    dm.getColumns().add(dc);
                }
                rs.close();
            }else{
                //主键
                Set<String> primaryKeys = new HashSet<>();//dm.getPrimaryKeys();
                ResultSet rs = meta.getPrimaryKeys(catalog, schema, table);
                while (rs.next()) {
                    String cn = rs.getString("COLUMN_NAME");
                    primaryKeys.add(cn);
                }
                ResultSet colMetaRs = null;
                try {
                    colMetaRs = meta.getColumns(catalog, schema, table, "%");
                    ResultSetMetaData rm = colMetaRs.getMetaData();
                    int columnCount = rm.getColumnCount();
                    while (colMetaRs.next()) {
                        Column dc = new Column();
                        dc.setCode(colMetaRs.getString("COLUMN_NAME"));
                        dc.setPrimaryKey(primaryKeys.contains(dc.getCode()));
                        dc.setDataType(colMetaRs.getString("TYPE_NAME"));
                        dc.setLength(colMetaRs.getInt("COLUMN_SIZE"));
                        dc.setVisibleInForm(true);
                        dc.setVisibleInList(true);
                        int dataType = colMetaRs.getInt("DATA_TYPE");
                        setType(dataType,dc);
                        dc.setType(getSqlDataType(dataType));
                        dc.setName(colMetaRs.getString("REMARKS"));
                        if(StringUtil.isBlank(dc.getName())){
                            dc.setName(dc.getCode());
                        }
                        dc.setMandatory(!"YES".equals(colMetaRs.getString("IS_NULLABLE")));
                        if(!dc.isPrimaryKey()&&dc.isMandatory()){//默认非空字段作为搜索项
                            dc.setSearchCondition(true);
                        }
                        dm.getColumns().add(dc);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (colMetaRs != null) {
                        colMetaRs.close();
                    }
                }
            }

            return dm;
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }
    public static String getSqlDataType(int dataType){
        switch (dataType) {
            case Types.NUMERIC:
                return "NUMERIC";
            case Types.DECIMAL:
                return "DECIMAL";
            case Types.FLOAT:
                return "FLOAT";
            case Types.DOUBLE:
                return "DOUBLE";
            case Types.REAL:
                return "REAL";
            case Types.BIT:
                return "BIT";
            case Types.BOOLEAN:
                return "BOOLEAN";
            case Types.TINYINT:
                return "TINYINT";
            case Types.SMALLINT:
                return "SMALLINT";
            case Types.INTEGER:
                return "INTEGER";
            case Types.BIGINT:
                return "BIGINT";
            case Types.CHAR:
                return "CHAR";
            case Types.VARCHAR:
                return "VARCHAR";
            case Types.LONGVARCHAR:
                return "LONGVARCHAR";
            case Types.CLOB:
                return "CLOB";
            case Types.NCLOB:
                return "NCLOB";
            case Types.BINARY:
                return "BINARY";
            case Types.VARBINARY:
                return "VARBINARY";
            case Types.LONGVARBINARY:
                return "LONGVARBINARY";
            case Types.BLOB:
                return "BLOB";
            case Types.DATE:
                return "DATE";
            case Types.TIME:
                return "TIME";
            case Types.TIMESTAMP:
                return "TIMESTAMP";
            default:
                return "OTHER";
        }
    }
    private static void setType(int dataType,Column dc){
        //类型和控件
        switch (dataType) {
            case Types.NUMERIC:
            case Types.DECIMAL:
            case Types.FLOAT:
            case Types.DOUBLE:
            case Types.REAL:
                dc.setDataType("BigDecimal");
                dc.setControlType("Number");
                break;
            case Types.BIT:
            case Types.BOOLEAN:
                dc.setDataType("Boolean");
                dc.setControlType("Switch");
                break;
            case Types.TINYINT:
            case Types.SMALLINT:
            case Types.INTEGER:
                dc.setDataType("Integer");
                dc.setControlType("Number");
                break;
            case Types.BIGINT:
                dc.setDataType("Long");
                dc.setControlType("Number");
                break;
            case Types.CHAR:
            case Types.VARCHAR:
                dc.setDataType("String");
                if (dc.getLength() >= 500) {
                    dc.setControlType("TextArea");
                } else {
                    dc.setControlType("Text");
                }
                break;
            case Types.LONGVARCHAR:
            case Types.CLOB:
            case Types.NCLOB:
                dc.setDataType("String");
                dc.setControlType("TextArea");
                break;
            case Types.BINARY:
            case Types.VARBINARY:
            case Types.LONGVARBINARY:
            case Types.BLOB:
                dc.setDataType("byte[]");
                dc.setControlType("file");
                break;
            case Types.DATE:
            case Types.TIME:
            case Types.TIMESTAMP:
                dc.setDataType("Date");
                dc.setControlType("Date");
                break;
            default:
                dc.setDataType("String");
                dc.setControlType("Text");
        }

    }
    private void write(File file,String content){
        try {
            FileUtil.putStrToFile(content,file.getAbsolutePath(),"UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private String read(File file){
        try {
            return FileUtil.readFileAsStr(file,"UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }
    }
}
